package com.anta.it.hr.devtools.core.rule;

import com.anta.it.hr.devtools.core.request.RequestInetAddress;
import com.netflix.client.config.IClientConfig;
import com.netflix.loadbalancer.*;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.discovery.DiscoveryClient;

import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.*;
import java.util.concurrent.ThreadLocalRandom;
import java.util.stream.Collectors;

/**
 * @author wangxiaoxian
 * @since 2021/7/6
 */
@Slf4j
public class WhitelistRule extends AbstractLoadBalancerRule {

    private final RoundRobinRule roundRobinRule = new RoundRobinRule();
    private DiscoveryClient discoveryClient;
    private RequestInetAddress address;
    private final String METADATA_KEY_WHITELIST = "whitelist";//eureka.instance.metadata-map下的key
    private final String METADATA_KEY_SPLIT = "[;,|]";//支持的分割符

    /**
     * 1.所有服务器都没限定访问者，采用轮询方式；<br/>
     * 2.部分服务器限定：
     * <ol>
     *  <li>当前访问者匹配到其中的限定主机，随机返回一台；
     *  <li>当前访问者匹配不到其中的限定主机，取其他没有限定的主机，随机返回一台；
     *  <li>当前访问者匹配不到其中的限定主机，且找不到其他可用的主机，返回空
     * <ol/>
     */
    public Server choose(ILoadBalancer lb, Object key) {
        if (lb == null) {
            log.error("no load balancer");
            return null;
        }
        String requestIp = address.getIp(key);
        log.info("request ip:{}", requestIp);
        if (requestIp == null) {
            log.info("Request IP is null, select by RoundRobinRule");
            return roundRobinRule.choose(key);
        }
        BaseLoadBalancer baseLoadBalancer = (BaseLoadBalancer)lb;
        log.info("select service by id :{}", baseLoadBalancer.getName());
        List<ServiceInstance> instanceList = discoveryClient.getInstances(baseLoadBalancer.getName());//注册中心中对应服务ID的所有服务实例，不考虑它们的健康状态。为了取得配置项whitelist
        List<Server> reachableServers = lb.getReachableServers();//经过负载均衡器筛选过的健康的、可用于接收请求的服务实例
        if (CollectionUtils.isEmpty(instanceList) || CollectionUtils.isEmpty(reachableServers)) {
            return null;
        }
        //转化为map，key=ip+端口（服务）,value=允许访问的ip列表。即每台服务器可被访问的ip列表
        Map<String, List<String>> whitelistMap = toWhitelistMap(instanceList);
        if (whitelistMap.size() == 0) {
            log.info("all services is free, select by RoundRobinRule");
            return roundRobinRule.choose(key);
        } else {
            //允许本机访问的服务器列表
            List<String> whitelistServerList = getWhitelistServer(whitelistMap, requestIp);
            if (CollectionUtils.isNotEmpty(whitelistServerList)) {
                //限定的服务器中，有本机可以访问的服务器，则随机获取一台
                return getTargetServer(reachableServers, whitelistServerList);
            }
            log.info("select a random service from the rest");
            return getRandomServer(getOtherServer(reachableServers, whitelistMap));
        }
    }

    private List<Server> getOtherServer(List<Server> reachableServers, Map<String, List<String>> limitCallerMap) {
        return reachableServers.stream().filter(r -> !limitCallerMap.containsKey(r.getHostPort())).collect(Collectors.toList());
    }

    private Server getTargetServer(List<Server> reachableServers, List<String> whitelistServerList) {
        List<Server> resultList = new ArrayList<>(4);
        reachableServers.forEach(r -> {
            if (whitelistServerList.contains(r.getHostPort())) {
                resultList.add(r);
            }
        });
        return getRandomServer(resultList);
    }

    private Server getRandomServer(List<Server> resultList) {
        if (CollectionUtils.isEmpty(resultList)) {
            return null;
        }
        Server target = resultList.get(ThreadLocalRandom.current().nextInt(resultList.size()));
        log.info("get target service：{}", target.getHostPort());
        return target;
    }

    private List<String> getWhitelistServer(Map<String, List<String>> whitelistMap, String requestIp) {
        List<String> whitelistServerList = new ArrayList<>(4);
        whitelistMap.forEach((key, value) -> {
            if (value.contains(requestIp)) {
                whitelistServerList.add(key);
            }
        });
        return whitelistServerList;
    }

    private Map<String, List<String>> toWhitelistMap(List<ServiceInstance> instanceList) {
        Map<String, List<String>> whitelistMap = new HashMap<>(instanceList.size());
        instanceList.forEach(ins -> {
            Map<String, String> metadata = ins.getMetadata();
            String whitelistStr = metadata.get(METADATA_KEY_WHITELIST);
            if (StringUtils.isNotBlank(whitelistStr)) {
                String[] whitelistArray = whitelistStr.split(METADATA_KEY_SPLIT);
                whitelistMap.put(ins.getHost() + ":" + ins.getPort(), Arrays.asList(whitelistArray));
            }
        });
        return whitelistMap;
    }

    @Override
    public Server choose(Object key) {
        return choose(super.getLoadBalancer(), key);
    }

    @Override
    public void initWithNiwsConfig(IClientConfig clientConfig) {

    }
    @Override
    public void setLoadBalancer(ILoadBalancer lb) {
        super.setLoadBalancer(lb);
        roundRobinRule.setLoadBalancer(lb);
    }

    public void setDiscoveryClient(DiscoveryClient discoveryClient) {
        this.discoveryClient = discoveryClient;
    }

    public void setAddress(RequestInetAddress address) {
        this.address = address;
    }

    /**
     * 获取本机电脑名，可扩展按电脑名限制
     */
    private String getRequestLocalName(String ip) {
        InetAddress inet;
        try {
            inet = InetAddress.getByName(ip);
            return inet.getHostName();
        } catch (UnknownHostException e) {
            e.printStackTrace();
        }
        return null;
    }
}
